/*
 * RemoteTableEditor.java
 *
 * Written by Joseph Bowbeer and released to the public domain,
 * as explained at http://creativecommons.org/licenses/publicdomain
 */

package jozart.remotetabledemo;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;

import jozart.remotetable.RemoteTable;
import jozart.remotetable.RemoteTableModel;
import jozart.remotetable.RemoteTableTaskEvent;
import jozart.remotetable.RemoteTableTaskListener;
import jozart.swingutils.Blip;
import jozart.swingutils.SwingWorker;

/**
 * Edits a remote table model.
 * 
 * @author Joseph Bowbeer
 * @version 2.1
 */
public class RemoteTableEditor extends JApplet {

    private JScrollPane jScrollPane;
    private RemoteTable remoteTable;
    private JPanel jPanel;
    private JLabel jlblStatus;
    private Blip blip;

    /** Number of active tasks. */
    private int activeTasks;

    /** Initializes the form. */
    public RemoteTableEditor() {
        initComponents();

        /* Setup local table model, listen for changes. */
        DefaultTableModel model = createLocalModel();
        model.addTableModelListener(new TableModelListener() {
            public void tableChanged(TableModelEvent evt) {
                localTableChanged(evt);
            }
        });
        remoteTable.setModel(model);
    }

    /**
     * Called from within the constructor to initialize the form.
     */
    private void initComponents() {
        jScrollPane = new JScrollPane();
        remoteTable = new RemoteTable();
        jPanel = new JPanel();
        jlblStatus = new JLabel();
        blip = new Blip();

        remoteTable.setModel(new DefaultTableModel (
            new Object[][] { },
            new String[] { }
        ));
        remoteTable.setPreferredScrollableViewportSize(new Dimension(450, 150));
        remoteTable.addRemoteTableTaskListener(new RemoteTableTaskListener() {
            public void taskStarted(RemoteTableTaskEvent evt) {
                remoteTableTaskStarted(evt);
            }
            public void taskEnded(RemoteTableTaskEvent evt) {
                remoteTableTaskEnded(evt);
            }
        });
        jScrollPane.setViewportView(remoteTable);

        getContentPane().add(jScrollPane, BorderLayout.CENTER);

        jPanel.setLayout(new BorderLayout());

        jlblStatus.setBorder(new CompoundBorder(
            new CompoundBorder(
                new EmptyBorder(new Insets(1, 1, 1, 1)),
                new LineBorder(Color.GRAY)),
            new EmptyBorder(new Insets(0, 2, 0, 0))));
        jlblStatus.setText("Idle");
        jlblStatus.setToolTipText("Status");
        jPanel.add(jlblStatus, BorderLayout.CENTER);

        blip.setPreferredSize(new Dimension(23, 23));
        blip.setBorder(new CompoundBorder(
            new CompoundBorder(
                new EmptyBorder(new Insets(1, 1, 1, 1)),
                new LineBorder(Color.GRAY)),
            new EmptyBorder(new Insets(1, 1, 1, 1))));
        blip.setToolTipText("Activity Indicator");
        blip.setForeground(new Color(153, 153, 204));
        blip.setOpaque(true);
        jPanel.add(blip, BorderLayout.EAST);

        getContentPane().add(jPanel, BorderLayout.SOUTH);
    }

    /**
     * Updates status line and starts blip when a task is started.
     */
    private void remoteTableTaskStarted(RemoteTableTaskEvent evt) {
        switch (evt.getType()) {
          case RemoteTableTaskEvent.CONNECT:
            jlblStatus.setText("Connecting...");
            break;
          case RemoteTableTaskEvent.DISCONNECT:
            jlblStatus.setText("Disconnecting...");
            break;
          case RemoteTableTaskEvent.UPDATE:
            jlblStatus.setText("Updating server...");
            break;
        }
        if (activeTasks++ == 0) {
            blip.start();
        }
    }

    /**
     * Updates status line and stops blip when a task ends.
     */
    private void remoteTableTaskEnded(RemoteTableTaskEvent evt) {
        if (--activeTasks == 0) {
            blip.stop();
        }
        SwingWorker<?> task = evt.getTask();
        try {
            task.get(); // check if task succeeded
            switch (evt.getType()) {
              case RemoteTableTaskEvent.CONNECT:
                jlblStatus.setText("Connected");
                break;
              case RemoteTableTaskEvent.DISCONNECT:
                jlblStatus.setText("Disconnected");
                break;
              case RemoteTableTaskEvent.UPDATE:
                jlblStatus.setText("Update complete");
                break;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            switch (evt.getType()) {
              case RemoteTableTaskEvent.CONNECT:
                jlblStatus.setText("Connect failed");
                break;
              case RemoteTableTaskEvent.DISCONNECT:
                jlblStatus.setText("Disconnect failed");
                break;
              case RemoteTableTaskEvent.UPDATE:
                jlblStatus.setText("Update failed");
                break;
            }
        }
    }

    /**
     * Updates status line when local table changes.
     */
    private void localTableChanged(TableModelEvent evt) {
        switch (evt.getType()) {
          case TableModelEvent.DELETE:
            jlblStatus.setText("Rows removed");
            break;
          case TableModelEvent.INSERT:
            jlblStatus.setText("Rows inserted");
            break;
          case TableModelEvent.UPDATE:
            int row = evt.getFirstRow();
            int lastRow = evt.getLastRow();
            int col = evt.getColumn();
            if (lastRow == row && col != TableModelEvent.ALL_COLUMNS) {
                jlblStatus.setText("Cell ("+row+","+col+") updated");
            } else {
                jlblStatus.setText("Cells updated");
            }
            break;
        }
    }

    /**
     * Connects to remote model. Called on system thread.
     */
    public void start() {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                connect();
            }
        });
    }

    /**
     * Disconnects from remote model. Called on system thread.
     */
    public void stop() {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                disconnect();
            }
        });
    }

    /**
     * Connects the RemoteTable editor to its remote model.
     * 
     * @return connect task
     */
    public SwingWorker<RemoteTable.Connection> connect() {
        try {
            RemoteTableModel remoteModel = lookupRemoteModel();
            return remoteTable.connect(remoteModel);
        } catch (Exception ex) {
            jlblStatus.setText("Connect failed");
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * Disconnects the RemoteTable editor from its remote model.
     * 
     * @return disconnect task
     */
    public SwingWorker<RemoteTable.Connection> disconnect() {
        return remoteTable.disconnect();
    }

    /**
     * Disconnects RemoteTable editor from remote model and waits
     * up to <tt>timeout</tt> milliseconds for it to complete. Called
     * from <tt>main</tt> method just before calling <tt>exit</tt>.
     * 
     * @param timeout milliseconds to wait for shutdown to complete
     */
    public void shutdown(int timeout) {
        SwingWorker<RemoteTable.Connection> task = remoteTable.disconnect();
        if (task != null) {
            try {
                task.get(timeout, MILLISECONDS);
            } catch (Exception ex) {
            }
        }
    }

    /**
     * Creates local table model. Override to return a table model of your
     * choice.
     * <p>
     * Note: The remote model provides the row data, but the local and remote
     * models must agree on the column structure.
     * 
     * @return DefaultTableModel instance
     */
    protected DefaultTableModel createLocalModel() {
        return DemoTableModel.createClientModel(true); // editable
    }

    /**
     * Locates default table model server. Override to locate a table model
     * server of your choice.
     * 
     * @return remote table model proxy
     */
    protected RemoteTableModel lookupRemoteModel()
        throws MalformedURLException, NotBoundException, RemoteException {
        return lookupRemoteModel("/RemoteTableServer");
    }

    /**
     * Lookup remote table model given its name.
     * 
     * @param name name to locate
     * @return remote table model proxy
     */
    protected RemoteTableModel lookupRemoteModel(String name)
        throws MalformedURLException, NotBoundException, RemoteException {
        jlblStatus.setText("Looking up \"" + name + "\".");
        return (RemoteTableModel) Naming.lookup(name);
    }


    /**
     * Launches this applet in a frame.
     * @param args ignored
     */
    public static void main(String[] args) {

        /* Create and install a security manager. */
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }

        final JFrame frame = new JFrame("Remote Table Editor");
        final RemoteTableEditor applet = new RemoteTableEditor();
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                applet.shutdown(1000);
                System.exit(0);
            }
        });
        frame.getContentPane().add(applet, BorderLayout.CENTER);
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.pack();
                frame.setVisible(true);
                applet.init();
                applet.start();
            }
        });
    }
}
